package com.immediatus.engine.image;

import com.immediatus.graphics.texture.region.TabledTextureRegion;
import com.immediatus.graphics.vertex.RectangleVertexBuffer;

import java.util.Arrays;


public class AnimatedMultiImageLabel extends MultiImageLabel{
	private static final int LOOP_CONTINUOUS = -1;
    private static final long NANOSECONDSPERMILLISECOND = 1000000;
    private static final long NANOSECONDSPERSECOND = 1000000000;

    private boolean _running;
	private long _progress;
	private long _duration;

	private int _firstCellIndex;
	private int _initialLoopCount;
	private int _loopCount;
	private int _frameCount;

	private int[] _frames;
    private long[] _frameEndsInNanoseconds;

    private IAnimationListener _animationListener;

	public AnimatedMultiImageLabel(final float x_, final float y_, final TabledTextureRegion textureRegion_) {
		super(x_, y_, textureRegion_);
	}

	public AnimatedMultiImageLabel(final float x_, final float y_, final float w_, final float h_, final TabledTextureRegion textureRegion_) {
		super(x_, y_, w_, h_, textureRegion_);
	}

	public AnimatedMultiImageLabel(final float x_, final float y_, final TabledTextureRegion textureRegion_, final RectangleVertexBuffer vertexBuffer_) {
		super(x_, y_, textureRegion_, vertexBuffer_);
	}

	public AnimatedMultiImageLabel(final float x_, final float y_, final float w_, final float h_, final TabledTextureRegion tabledTextureRegion_, final RectangleVertexBuffer vertexBuffer_) {
		super(x_, y_, w_, h_, tabledTextureRegion_, vertexBuffer_);
	}


	public boolean isRunning() {
		return this._running;
	}

	public void stopAnimation() {
		this._running = false;
	}

	public void stopAnimation(final int index_) {
		this._running = false;
		this.setCurrentCellIndex(index_);
	}

	private int calculateCurrentFrameIndex() {
		final long animationProgress = this._progress;
		final long[] frameEnds = this._frameEndsInNanoseconds;
		final int frameCount = this._frameCount;
		for(int i = 0; i < frameCount; i++) {
			if(frameEnds[i] > animationProgress) {
				return i;
			}
		}

		return frameCount - 1;
	}

	public void animate(final long frameDurationEach_) {
		this.animate(frameDurationEach_, true);
	}

	public void animate(final long frameDurationEach_, final boolean loop_) {
		this.animate(frameDurationEach_, (loop_) ? LOOP_CONTINUOUS : 0, null);
	}

	public void animate(final long frameDurationEach_, final int count_) {
		this.animate(frameDurationEach_, count_, null);
	}

	public void animate(final long frameDurationEach_, final boolean loop_, final IAnimationListener listener_) {
		this.animate(frameDurationEach_, (loop_) ? LOOP_CONTINUOUS : 0, listener_);
	}

	public void animate(final long frameDurationEach_, final int loopCount_, final IAnimationListener listener_) {
		final long[] frameDurations = new long[this.getTextureRegion().getCellCount()];
		Arrays.fill(frameDurations, frameDurationEach_);
		this.animate(frameDurations, loopCount_, listener_);
	}

	public void animate(final long[] frameDuration_) {
		this.animate(frameDuration_, true);
	}

	public void animate(final long[] frameDuration_, final boolean loop_) {
		this.animate(frameDuration_, (loop_) ? LOOP_CONTINUOUS : 0, null);
	}

	public void animate(final long[] frameDuration_, final int count_) {
		this.animate(frameDuration_, count_, null);
	}

	public void animate(final long[] frameDuration_, final boolean loop_, final IAnimationListener listener_) {
		this.animate(frameDuration_, (loop_) ? LOOP_CONTINUOUS : 0, listener_);
	}

	public void animate(final long[] frameDuration_, final int count_, final IAnimationListener listener_) {
		this.animate(frameDuration_, 0, this.getTextureRegion().getCellCount() - 1, count_, listener_);
	}

	public void animate(final long[] frameDuration_, final int firstIndex_, final int lastIndex_, final boolean loop_) {
		this.animate(frameDuration_, firstIndex_, lastIndex_, (loop_) ? LOOP_CONTINUOUS : 0, null);
	}

	public void animate(final long[] frameDuration_, final int firstIndex_, final int lastIndex_, final int count_) {
		this.animate(frameDuration_, firstIndex_, lastIndex_, count_, null);
	}

	public void animate(final long[] frameDuration_, final int[] frames_, final int count_) {
		this.animate(frameDuration_, frames_, count_, null);
	}

	public void animate(final long[] frameDuration_, final int[] frames_, final int count_, final IAnimationListener listener_) {
		final int frameCount = frames_.length;
		if(frameDuration_.length != frameCount) {
			throw new IllegalArgumentException("frameDuration must have the same length as frames");
		}

		this.init(frameDuration_, frameCount, frames_, 0, count_, listener_);
	}

	public void animate(final long[] frameDuration_, final int firstIndex_, final int lastIndex_, final int count_, final IAnimationListener listener_) {
		if(lastIndex_ - firstIndex_ < 1) {
			throw new IllegalArgumentException("An animation needs at least two tiles to animate between");
		}

		final int frameCount = (lastIndex_ - firstIndex_) + 1;
		if(frameDuration_.length != frameCount) {
			throw new IllegalArgumentException("frameDuration must have the same length as firstIndex to lastIndex");
		}

		this.init(frameDuration_, frameCount, null, firstIndex_, count_, listener_);
	}

	private void init(final long[] durations_, final int frameCount_, final int[] frames_, final int firstIndex_, final int count_, final IAnimationListener listener_) {
		this._frameCount = frameCount_;
		this._animationListener = listener_;
		this._initialLoopCount = count_;
		this._loopCount = count_;
		this._frames = frames_;
		this._firstCellIndex = firstIndex_;

		if(this._frameEndsInNanoseconds == null || this._frameCount > this._frameEndsInNanoseconds.length) {
			this._frameEndsInNanoseconds = new long[this._frameCount];
		}

		final long[] frameEndsInNanoseconds = this._frameEndsInNanoseconds;
        arraySumInto(durations_, frameEndsInNanoseconds, NANOSECONDSPERMILLISECOND);

        this._duration = frameEndsInNanoseconds[this._frameCount - 1];

		this._progress = 0;
		this._running = true;
	}

    @Override
    protected void onManagedUpdate(final float secondsElapsed_) {
        super.onManagedUpdate(secondsElapsed_);
        if(this._running) {
            final long nanoSecondsElapsed = (long) (secondsElapsed_ * NANOSECONDSPERSECOND);
            this._progress += nanoSecondsElapsed;

            if(this._progress > this._duration) {
                this._progress %= this._duration;
                if(this._initialLoopCount != LOOP_CONTINUOUS) {
                    this._loopCount--;
                }
            }

            if(this._initialLoopCount == LOOP_CONTINUOUS || this._loopCount >= 0) {
                final int currentFrameIndex = this.calculateCurrentFrameIndex();

                if(this._frames == null) {
                    this.setCurrentCellIndex(this._firstCellIndex + currentFrameIndex);
                } else {
                    this.setCurrentCellIndex(this._frames[currentFrameIndex]);
                }
            } else {
                this._running = false;
                if(this._animationListener != null) {
                    this._animationListener.onAnimationEnd(this);
                }
            }
        }
    }

    private void arraySumInto(final long[] values_, final long[] targetValues_, final long factor_) {
        targetValues_[0] = values_[0] * factor_;
        final int valueCount = values_.length;
        for(int i = 1; i < valueCount; i++) {
            targetValues_[i] = targetValues_[i-1] + values_[i] * factor_;
        }
    }

	public static interface IAnimationListener {
		public void onAnimationEnd(final AnimatedMultiImageLabel animatedMultiImageLabel_);
	}
}
